package jp.dodododo.aop.interceptors;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import jp.dodododo.aop.annotation.Enhance;
import jp.dodododo.aop.annotation.HandleThrowable;
import jp.dodododo.aop.exception.IllegalMethodException;
import jp.dodododo.aop.exception.MethodNotFoundException;

import org.aopalliance.intercept.Invocation;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * {@.en }
 * 
 * <br />
 * 
 * {@.ja 例外処理用の{@link MethodInterceptor}です。}
 * 
 * @author Satoshi Kimura
 * 
 */
@Enhance(false)
public abstract class ThrowsInterceptor extends BothInterceptor {

	/**
	 * {@.en }
	 * 
	 * <br />
	 * 
	 * {@.ja {@link ThrowsInterceptor}の実装クラスが実装しなければいけないメソッド名です。}
	 */
	public static final String HANDLE_METHOD_NAME = "handle";

	@SuppressWarnings("unchecked")
	private Map<Class, Method> methodMap = new HashMap<Class, Method>();

	/**
	 * {@.en Constructs ThrowsInterceptor.}
	 * 
	 * <br />
	 * 
	 * {@.ja {@link ThrowsInterceptor}を構築します。}
	 */
	public ThrowsInterceptor() {
		Method[] methods = getClass().getMethods();
		for (int i = 0; i < methods.length; ++i) {
			Method m = methods[i];
			if (m.isBridge() || m.isSynthetic()) {
				continue;
			}
			if (isHandleThrowable(m)) {
				methodMap.put(m.getParameterTypes()[0], m);
			}
		}
		if (methodMap.size() == 0) {
			throw new MethodNotFoundException(getClass(), HANDLE_METHOD_NAME);
		}
	}

	private boolean isHandleThrowable(Method m) {
		if ((HANDLE_METHOD_NAME.equals(m.getName()) || m.getAnnotation(HandleThrowable.class) != null)) {
			if (m.getParameterTypes().length == 2 && Throwable.class.isAssignableFrom(m.getParameterTypes()[0])
					&& MethodInvocation.class.isAssignableFrom(m.getParameterTypes()[1])) {
				return true;
			} else {
				throw new IllegalMethodException("引数は、(? extends Throwable, ? extends MethodInvocation)の2つです。" + m.toGenericString());
			}
		} else {
			return false;
		}

	}

	/**
	 * @see jp.dodododo.aop.interceptors.BothInterceptor#process(org.aopalliance.intercept.Invocation)
	 */
	@Override
	protected Object process(Invocation invocation) throws Throwable {
		try {
			return invocation.proceed();
		} catch (Throwable t) {
			Method method = getMethod(t);
			if (method != null) {
				try {
					return method.invoke(this, new Object[] { t, invocation });
				} catch (InvocationTargetException ite) {
					throw ite.getTargetException();
				}
			}
			throw t;
		}
	}

	private Method getMethod(Throwable t) {
		Class<?> clazz = t.getClass();
		Method method = methodMap.get(clazz);
		while (method == null && clazz.equals(Throwable.class) == false) {
			clazz = clazz.getSuperclass();
			method = methodMap.get(clazz);
		}
		return method;
	}
}
